﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;
using System.Xml;
using System.Collections;
using System.Reflection;

namespace NLBS.Urls
{
    /// <summary>
    /// Url rewriting data store
    /// </summary>
    public class UrlsData
    {
        /// <summary>
        /// Instantiates a UrlsData data store
        /// </summary>
        /// <param name="doc">XmlDocument containing url rewriting configuration data</param>
        /// <param name="globalPath">Root path to the application utilizing Url rewriting</param>
        public UrlsData(XmlDocument doc, string globalPath)
        {

            _paths = new NameValueCollection();
            _reversePaths = new NameValueCollection();
            Initialize(doc, globalPath);
        }

        /// <summary>
        /// Initializes this UrlsData data store.  This method is called by the UrlsData constructor.
        /// </summary>
        /// <param name="doc">XmlDocument containing url rewriting configuration data</param>
        /// <param name="globalPath">Root path to the application utilizing Url rewriting</param>
        protected void Initialize(XmlDocument doc, string globalPath)
        {
            if (globalPath != null)
                globalPath = globalPath.Trim();

            XmlNode transformers = doc.SelectSingleNode("SiteUrls/transformers");
            ListDictionary transforms = CreateTransformers(transformers);

            XmlNode basePaths = doc.SelectSingleNode("SiteUrls/locations");
            _locationSet = CreateLocationSet(basePaths, globalPath, transforms);
            _locationFilter = _locationSet.Filter;


            #region SiteUrls



            //XmlNode urls = doc.SelectSingleNode("SiteUrls/urls");


            #endregion


            //CreateMappings(doc.SelectSingleNode("SiteUrls/mappings"), globalPath);
        }

        #region Mappings
        private void CreateMappings(XmlNode maps, string globalPath)
        {
            if (maps != null)
            {
                foreach (XmlNode map in maps.ChildNodes)
                {
                    if (map != null && map.NodeType != XmlNodeType.Comment)
                    {
                        XmlAttribute l = map.Attributes["location"];
                        XmlAttribute a = map.Attributes["path"];

                        if (l != null && a != null)
                        {
                            string path = (globalPath + "/" + a.Value).Replace("//", "/");
                            ApplicationKeyMapping am = new ApplicationKeyMapping(l.Value, path);
                            _mappings.Add(am);
                        }
                    }
                }
            }
        }
        #endregion


        #region Transformers
        private static ListDictionary CreateTransformers(XmlNode transformers)
        {
            ListDictionary transforms = new ListDictionary();
            foreach (XmlNode n in transformers.ChildNodes)
            {
                if (n.NodeType != XmlNodeType.Comment)
                {
                    string k = n.Attributes["key"].Value;
                    string v = n.Attributes["value"].Value;

                    if (!string.IsNullOrEmpty(k))
                    {
                        transforms[k] = v;
                    }
                }
            }

            return transforms;
        }
        #endregion

        #region Urls
        private void CreateUrls(string locName, Location loc, XmlNode urls, ListDictionary transforms, string globalPath)
        {
            foreach (XmlNode n in urls.ChildNodes)
            {

                if (n.NodeType != XmlNodeType.Comment && n.Name == "url")
                {
                    string name = n.Attributes["name"].Value;


                    XmlAttribute direct = n.Attributes["navigateUrl"];
                    if (direct != null)
                    {
                        _paths.Add(name, direct.Value);
                    }
                    else
                    {
                        string path = n.Attributes["path"].Value;

                        foreach (string key in transforms.Keys)
                        {
                            path = path.Replace(key, transforms[key].ToString());
                        }

                        _paths.Add(name, loc.Path + path);

                        // Store the full path to avoid having key duplicates
                        //
                        string keyPath = loc.Path + path;
                        if (globalPath.Length > 0)
                            keyPath = keyPath.Replace(globalPath, "").ToLower();
                        _reversePaths.Add(keyPath, name);

                        XmlAttribute vanity = n.Attributes["vanity"];
                        XmlAttribute pattern = n.Attributes["pattern"];
                        XmlAttribute defaultPage = n.Attributes["page"];
                        XmlAttribute physicalPath = n.Attributes["physicalPath"];
                        XmlAttribute redirect = n.Attributes["redirect"];


                        //Store full paths like regular urls. 
                        if (vanity != null && pattern != null)
                        {
                            string p = (!pattern.Value.StartsWith("/") ? loc.Path : String.Empty) + pattern.Value;
                            string v = null;

                            if (physicalPath == null)
                            {
                                v = (!vanity.Value.StartsWith("/") ? loc.PhysicalPath : String.Empty) + vanity.Value;
                            }
                            else
                            {
                                v = globalPath + physicalPath.Value + vanity.Value;
                            }
                            string page = defaultPage == null ? null : defaultPage.Value;

                            foreach (string key in transforms.Keys)
                            {
                                p = p.Replace(key, transforms[key].ToString());
                                v = v.Replace(key, transforms[key].ToString());
                            }


                            bool isRedirect = redirect != null ? redirect.Value == "true" : false;

                            loc.Add(new ReWrittenUrl(locName + "." + name, p, v, page, isRedirect));
                        }
                    }

                }

            }
        }

        #endregion

        #region LocationSet
        private LocationSet CreateLocationSet(XmlNode basePaths, string globalPath, ListDictionary transforms)
        {
            LocationSet ls = new LocationSet();

            XmlAttribute defaultLocationAttribute = basePaths.Attributes["type"];

            Type defaultLocationType = null;
            if (defaultLocationAttribute != null)
                defaultLocationType = Type.GetType(defaultLocationAttribute.Value);
            else
                defaultLocationType = typeof(Location);

            if (defaultLocationType == null)
                throw new Exception("Could not load default location type");

            ConstructorInfo locationConstructor = defaultLocationType.GetConstructor(new Type[] { typeof(string), typeof(string), typeof(string), typeof(bool) });

            foreach (XmlNode n in basePaths.ChildNodes)
            {


                if (n.NodeType != XmlNodeType.Comment && n.Name == "location")
                {
                    XmlAttribute name = n.Attributes["name"];
                    XmlAttribute path = n.Attributes["path"];
                    XmlAttribute physicalPath = n.Attributes["physicalPath"];
                    XmlAttribute exclude = n.Attributes["exclude"];
                    XmlAttribute ltype = n.Attributes["type"];
                    XmlAttribute themeDir = n.Attributes["themeDir"];

                    if (name != null && path != null)
                    {
                        string pp = null;
                        if (physicalPath != null)
                        {
                            pp = globalPath + physicalPath.Value;
                        }

                        bool isExeclude = (exclude != null && bool.Parse(exclude.Value));
                        string themeDirectory = themeDir == null ? string.Empty : themeDir.Value;
                        Location l = null;
                        string vp = globalPath + path.Value;
                        if (ltype == null)
                        {
                            //l = locationConstructor.Invoke(new object[] { vp, pp, themeDirectory, isExeclude }) as Location;
                            l = new Location(vp, pp, themeDirectory, isExeclude);
                        }
                        else
                        {
                            Type t = Type.GetType(ltype.Value);
                            if (t == null)
                            {
                                throw new Exception("Type for custom location could not be loaded " + ltype.Value);
                            }

                            l = Activator.CreateInstance(t, new object[] { vp, pp, themeDirectory, isExeclude }) as Location;
                            if (l == null)
                            {
                                throw new Exception("Type for custom location could not be loaded as an instance of Location" + ltype.Value);
                            }
                        }
                        ls.Add(name.Value, l);

                        CreateUrls(name.Value, l, n, transforms, globalPath);
                    }
                }
            }

            return ls;
        }

        #endregion

        /// <summary>
        /// Formats a Url for the given Url name
        /// </summary>
        /// <param name="name">Name of the Url to format</param>
        /// <returns>Formatted Url</returns>
        public string FormatUrl(string name)
        {
            return FormatUrl(name, null);
        }

        /// <summary>
        /// Formats a Url for the given Url name and formatting parameters
        /// </summary>
        /// <param name="name">Name of the Url to format</param>
        /// <param name="parameters">Formatting parameters required by the Url matching the given name</param>
        /// <returns>Formatted Url including the given parameters</returns>
        public virtual string FormatUrl(string name, params object[] parameters)
        {


            if (parameters == null)
                return this.Paths[name];

            else
                return string.Format(Paths[name], parameters);
        }

        #region Public Properties
        protected NameValueCollection _paths = null;
        protected NameValueCollection _reversePaths = null;
        protected string _locationFilter;
        protected LocationSet _locationSet = null;
        protected ArrayList _mappings = new ArrayList();

        /// <summary>
        /// ApplicationKeyMappings of this UrlsData data store
        /// </summary>
        public ArrayList Mappings
        {
            get { return _mappings; }
        }

        /// <summary>
        /// LocationSet of this UrlsData data store
        /// </summary>
        public LocationSet LocationSet
        {
            get { return _locationSet; }
        }

        /// <summary>
        /// Regex pattern matching Urls that can be rewritten using data in this UrlsData data store
        /// </summary>
        public string LocationFilter
        {
            get
            {
                return _locationFilter;
            }
        }

        /// <summary>
        /// Url path formatting strings indexed by Url name
        /// </summary>
        public NameValueCollection Paths
        {
            get { return this._paths; }
        }

        /// <summary>
        /// Url names indexed by Url paths
        /// </summary>
        public NameValueCollection ReversePaths
        {
            get { return this._reversePaths; }
        }

        #endregion

    }
}
